#include "confparser.h"
#include "myinetd.h"
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/file.h>


static Service *service[MAX_NUM_SERVICES];
static int service_count = 0;
char err_message[MAX_ERR_MSG];

void log_message(char *filename, char *message)
{
  time_t mytime;
  FILE *logfile;
  char buf[50];

  if(filename)
    logfile=fopen(filename,"a");
  else
    logfile = stdout;
  if(!logfile) return;
  flock(fileno(logfile), LOCK_EX);


  mytime = time(NULL);
  sprintf(buf, "%s", ctime(&mytime));
  strtok(buf, "\n");
  fprintf(logfile,"%s>> %s\n", buf, message);


  flock(fileno(logfile), LOCK_UN);
  if(filename)
    fclose(logfile);
}

void signal_handler(int sig)
{
  char message[100];
  int pid, i, j, n;
  struct sockaddr_in sin;        

  memset((char *)&sin, 0, sizeof(sin));
  sin.sin_family = AF_UNSPEC;


  switch(sig) {
  case SIGHUP:
    log_message(LOG_FILE,"hangup signal catched");
    break;
  case SIGTERM:
    log_message(LOG_FILE, "TERM SIGNAL RECEIVED: myinetd ended");
    exit(0);
    break;
  case SIGCHLD:
    pid = wait(NULL);
    for(i=0; i<service_count; i++) {
      for(j=0; j<BACKLOG; j++) {
	if(service[i]->connection[j].pid == pid) {
	  service[i]->connection[j].pid = 0;
	  snprintf(message, 100, "Session ID %d ended", service[i]->connection[j].id);
	  log_message(LOG_FILE, message);
	  if(service[i]->type == SOCK_STREAM) {
	    shutdown(service[i]->connection[j].sockfd, SHUT_RDWR);
	    close(service[i]->connection[j].sockfd);
	  } else {
	    connect(service[i]->sockfd, (struct sockaddr *)&sin, sizeof(sin));
	  }
	  signal(SIGCHLD, signal_handler);
	  break;
	}
      }
    }
    break;
  }
}

void daemonize()
{
int i,lfp;
char str[10];
	if(getppid()==1) return; /* already a daemon */
	i=fork();
	if (i<0) exit(1); /* fork error */
	if (i>0) exit(0); /* parent exits */
	/* child (daemon) continues */
	setsid(); /* obtain a new process group */
	for (i=getdtablesize();i>=0;--i) close(i); /* close all descriptors */
	i=open("/dev/null",O_RDWR); dup(i); dup(i); /* handle standart I/O */
	umask(027); /* set newly created file permissions */
	chdir(RUNNING_DIR); /* change running directory */
	lfp=open(LOCK_FILE,O_RDWR|O_CREAT,0640);
	if (lfp<0) exit(1); /* can not open */
	if (lockf(lfp,F_TLOCK,0)<0) exit(0); /* can not lock */
	/* first instance continues */
	sprintf(str,"%d\n",getpid());
	write(lfp,str,strlen(str)); /* record pid to lockfile */	
	signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
	signal(SIGTTOU,SIG_IGN);
	signal(SIGTTIN,SIG_IGN);
	signal(SIGHUP,signal_handler); /* catch hangup signal */
	signal(SIGTERM,signal_handler); /* catch kill signal */
}


int main() {
  char message[100];  
  int i, j, pid, unique_id=0;
  struct sockaddr_in my_addr, their_addr;   
  int maxfd = 0, new_fd;
  int sin_size = sizeof(struct sockaddr_in);
  fd_set fds;
  
  daemonize();
  signal(SIGCHLD, signal_handler);

  log_message(LOG_FILE, "myinetd Started");
  confparser_open();
  while(service_count < MAX_NUM_SERVICES && (service[service_count] = confparser_getService()) != NULL) {
    if ((service[service_count]->sockfd = socket(AF_INET, service[service_count]->type, 0)) == -1) {
      snprintf(err_message, MAX_ERR_MSG, "Could not create socket for service '%s'.\n", service[service_count]->name);
      log_message(LOG_FILE, err_message);
      exit(1);
    }  
    service[service_count]->sockfd > maxfd ? maxfd = service[service_count]->sockfd : 0;
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(service[service_count]->port);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero), 8);
    if (bind(service[service_count]->sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
      snprintf(err_message, MAX_ERR_MSG,"Could not bind port = '%u' for service '%s'.\n", service[service_count]->port, service[service_count]->name);
      log_message(LOG_FILE, err_message);
      exit(1);
    }
    if(service[service_count]->type == SOCK_STREAM) {
      if(listen(service[service_count]->sockfd, BACKLOG) == -1) {
	snprintf(err_message, MAX_ERR_MSG,"Could not listen on socket for service '%s'.\n", service[service_count]->name);
	log_message(LOG_FILE, err_message);
	exit(1);
      }
    }
    service_count++;
  }
  confparser_close();

  for(i=0; i<service_count; i++) {
    for(j=0; j<BACKLOG; j++) {
      service[i]->connection[j].pid = 0;
    }
  }

  FD_ZERO(&fds);
  
  FOREVER {
    for(i=0; i<service_count; i++) {
      if(service[i]->type == SOCK_STREAM || service[i]->connection[0].pid == 0)
	FD_SET(service[i]->sockfd, &fds);
    }
    if(select(maxfd+1, &fds, NULL, NULL, NULL) < 0) {
      if (errno != EINTR) {
	perror("CRITICAL ERROR: Select Failed!\n");
	exit(1);
      } 
      
      continue;
    }
    for(i=0; i<service_count; i++) {
      if(FD_ISSET(service[i]->sockfd, &fds)) {
	if(service[i]->type == SOCK_STREAM) {	  
	  if ((new_fd = accept(service[i]->sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1){	      
	    exit(1);
	  }
	  snprintf(message, 100, "Client <%s, %u> requesting TCP service %s on port %u: Session ID = %d", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), service[i]->name, service[i]->port, unique_id);
	  log_message(LOG_FILE, message);
	  switch(pid=fork()) {
	  case -1:
	    snprintf(err_message, MAX_ERR_MSG, "CRITICAL ERROR: Failed to create Child process for service '%s'.\n", service[i]->name);
	    log_message(LOG_FILE, err_message);  
	    exit(1);
	    break;
	  case 0:
	    dup2(new_fd,0);
	    dup2(new_fd,1);
	    dup2(new_fd,2);
	    close(new_fd);
	    if(execv(service[i]->pathname, service[i]->args)) {
	      snprintf(err_message, MAX_ERR_MSG, "Failed to execute '%s' for service '%s'.\n", service[i]->pathname, service[i]->name);
	      log_message(LOG_FILE, err_message);  
	      exit(1);
	    }
	    break;
	  default:
	    for(j=0; j<BACKLOG; j++) {
	      if(service[i]->connection[j].pid == 0) {
		service[i]->connection[j].pid = pid;
		service[i]->connection[j].id = unique_id++;
		service[i]->connection[j].sockfd = new_fd;
		break;
	      }
	    }
	  }
	} else if(service[i]->type == SOCK_DGRAM) {
	  recvfrom(service[i]->sockfd, message, 100, MSG_PEEK, (struct sockaddr *)&their_addr, &sin_size);
	  connect(service[i]->sockfd,(struct sockaddr *)&their_addr,sin_size);

	  snprintf(message, 100, "Client <%s, %u> requesting UDP service %s on port %u: Session ID = %d", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), service[i]->name, service[i]->port, unique_id);
	  log_message(LOG_FILE, message);
	  switch(pid=fork()) {
	  case -1:
	    snprintf(err_message, MAX_ERR_MSG, "CRITICAL ERROR: Failed to create Child process for service '%s'.\n", service[i]->name);
	    log_message(LOG_FILE, err_message);
	    exit(1);
	    break;
	  case 0:
	    dup2(service[i]->sockfd,0);
	    dup2(service[i]->sockfd,1);
	    dup2(service[i]->sockfd,2);
	    if(execv(service[i]->pathname, service[i]->args)) {
	      snprintf(err_message, MAX_ERR_MSG, "Failed to execute '%s' for service '%s'.\n", service[i]->pathname, service[i]->name);
	      log_message(LOG_FILE, err_message);
	      exit(1);
	    }
	    break;
	  default:
	    FD_CLR(service[i]->sockfd, &fds);
	    service[i]->connection[0].pid = pid;
	    service[i]->connection[0].id = unique_id++;
	    break;
	  }
	  
	} else {
	  snprintf(err_message, MAX_ERR_MSG, "CRITICAL ERROR: Invalid socket type = '%d' for service '%s'.\nProbable buffer overflow!\n", service[i]->type, service[i]->name);
	  log_message(LOG_FILE, err_message);
	  exit(1);	  
	}	  
      }
    }
  }

  for(i=0; i<service_count; i++) {
    confparser_destroyService(service[i]);
  }
}
